﻿using System;
using System.Collections.Generic;
using GeoAPI.Geometries;
using NetTopologySuite.Utilities;

namespace NetTopologySuite.Index.Strtree
{
	[Serializable]
	public class STRtree<TItem> : AbstractSTRtree<Envelope, TItem>, ISpatialIndex<TItem>
	{
		public STRtree() : this(10)
		{
		}

		public STRtree(int nodeCapacity) : base(nodeCapacity)
		{
		}

		private static double Avg(double a, double b)
		{
			return (a + b) / 2.0;
		}

		private static double CentreX(Envelope e)
		{
			return STRtree<TItem>.Avg(e.MinX, e.MaxX);
		}

		private static double CentreY(Envelope e)
		{
			return STRtree<TItem>.Avg(e.MinY, e.MaxY);
		}

		protected override IList<IBoundable<Envelope, TItem>> CreateParentBoundables(IList<IBoundable<Envelope, TItem>> childBoundables, int newLevel)
		{
			Assert.IsTrue(childBoundables.Count != 0);
			int num = (int)Math.Ceiling((double)childBoundables.Count / (double)base.NodeCapacity);
			List<IBoundable<Envelope, TItem>> list = new List<IBoundable<Envelope, TItem>>(childBoundables);
			list.Sort(STRtree<TItem>.XComparer);
			IList<IBoundable<Envelope, TItem>>[] verticalSlices = this.VerticalSlices(list, (int)Math.Ceiling(System.Math.Sqrt((double)num)));
			return this.CreateParentBoundablesFromVerticalSlices(verticalSlices, newLevel);
		}

		private List<IBoundable<Envelope, TItem>> CreateParentBoundablesFromVerticalSlices(IList<IBoundable<Envelope, TItem>>[] verticalSlices, int newLevel)
		{
			Assert.IsTrue(verticalSlices.Length != 0);
			List<IBoundable<Envelope, TItem>> list = new List<IBoundable<Envelope, TItem>>();
			for (int i = 0; i < verticalSlices.Length; i++)
			{
				foreach (IBoundable<Envelope, TItem> item in this.CreateParentBoundablesFromVerticalSlice(verticalSlices[i], newLevel))
				{
					list.Add(item);
				}
			}
			return list;
		}

		protected IList<IBoundable<Envelope, TItem>> CreateParentBoundablesFromVerticalSlice(IList<IBoundable<Envelope, TItem>> childBoundables, int newLevel)
		{
			return base.CreateParentBoundables(childBoundables, newLevel);
		}

		protected IList<IBoundable<Envelope, TItem>>[] VerticalSlices(IList<IBoundable<Envelope, TItem>> childBoundables, int sliceCount)
		{
			int num = (int)Math.Ceiling((double)childBoundables.Count / (double)sliceCount);
			IList<IBoundable<Envelope, TItem>>[] array = new IList<IBoundable<Envelope, TItem>>[sliceCount];
			IEnumerator<IBoundable<Envelope, TItem>> enumerator = childBoundables.GetEnumerator();
			for (int i = 0; i < sliceCount; i++)
			{
				array[i] = new List<IBoundable<Envelope, TItem>>();
				int num2 = 0;
				while (num2 < num && enumerator.MoveNext())
				{
					IBoundable<Envelope, TItem> item = enumerator.Current;
					array[i].Add(item);
					num2++;
				}
			}
			return array;
		}

		protected override AbstractNode<Envelope, TItem> CreateNode(int level)
		{
			return new STRtree<TItem>.AnonymousAbstractNodeImpl(level);
		}

		protected override AbstractSTRtree<Envelope, TItem>.IIntersectsOp IntersectsOp
		{
			get
			{
				return STRtree<TItem>.IntersectsOperation;
			}
		}

		public new void Insert(Envelope itemEnv, TItem item)
		{
			if (itemEnv.IsNull)
			{
				return;
			}
			base.Insert(itemEnv, item);
		}

		public new IList<TItem> Query(Envelope searchEnv)
		{
			return base.Query(searchEnv);
		}

		public new void Query(Envelope searchEnv, IItemVisitor<TItem> visitor)
		{
			base.Query(searchEnv, visitor);
		}

		public new bool Remove(Envelope itemEnv, TItem item)
		{
			return base.Remove(itemEnv, item);
		}

		protected override IComparer<IBoundable<Envelope, TItem>> GetComparer()
		{
			return STRtree<TItem>.YComparer;
		}

		public TItem[] NearestNeighbour(IItemDistance<Envelope, TItem> itemDist)
		{
			return STRtree<TItem>.NearestNeighbour(new BoundablePair<TItem>(base.Root, base.Root, itemDist));
		}

		public TItem NearestNeighbour(Envelope env, TItem item, IItemDistance<Envelope, TItem> itemDist)
		{
			ItemBoundable<Envelope, TItem> boundable = new ItemBoundable<Envelope, TItem>(env, item);
			return STRtree<TItem>.NearestNeighbour(new BoundablePair<TItem>(base.Root, boundable, itemDist))[0];
		}

		public TItem[] NearestNeighbour(STRtree<TItem> tree, IItemDistance<Envelope, TItem> itemDist)
		{
			return STRtree<TItem>.NearestNeighbour(new BoundablePair<TItem>(base.Root, tree.Root, itemDist));
		}

		private static TItem[] NearestNeighbour(BoundablePair<TItem> initBndPair)
		{
			return STRtree<TItem>.NearestNeighbour(initBndPair, double.PositiveInfinity);
		}

		private static TItem[] NearestNeighbour(BoundablePair<TItem> initBndPair, double maxDistance)
		{
			double num = maxDistance;
			BoundablePair<TItem> boundablePair = null;
			PriorityQueue<BoundablePair<TItem>> priorityQueue = new PriorityQueue<BoundablePair<TItem>>();
			priorityQueue.Add(initBndPair);
			while (!priorityQueue.IsEmpty() && num > 0.0)
			{
				BoundablePair<TItem> boundablePair2 = priorityQueue.Poll();
				double distance = boundablePair2.Distance;
				if (distance >= num)
				{
					break;
				}
				if (boundablePair2.IsLeaves)
				{
					num = distance;
					boundablePair = boundablePair2;
				}
				else
				{
					boundablePair2.ExpandToQueue(priorityQueue, num);
				}
			}
			if (boundablePair != null)
			{
				return new TItem[]
				{
					((ItemBoundable<Envelope, TItem>)boundablePair.GetBoundable(0)).Item,
					((ItemBoundable<Envelope, TItem>)boundablePair.GetBoundable(1)).Item
				};
			}
			return null;
		}

		private static readonly STRtree<TItem>.AnonymousXComparerImpl XComparer = new STRtree<TItem>.AnonymousXComparerImpl();

		private static readonly STRtree<TItem>.AnonymousYComparerImpl YComparer = new STRtree<TItem>.AnonymousYComparerImpl();

		private static readonly AbstractSTRtree<Envelope, TItem>.IIntersectsOp IntersectsOperation = new STRtree<TItem>.AnonymousIntersectsOpImpl();

		private const int DefaultNodeCapacity = 10;

		private class AnonymousXComparerImpl : Comparer<IBoundable<Envelope, TItem>>
		{
			public override int Compare(IBoundable<Envelope, TItem> o1, IBoundable<Envelope, TItem> o2)
			{
				return AbstractSTRtree<Envelope, TItem>.CompareDoubles(STRtree<TItem>.CentreX(o1.Bounds), STRtree<TItem>.CentreX(o2.Bounds));
			}
		}

		private class AnonymousYComparerImpl : Comparer<IBoundable<Envelope, TItem>>
		{
			public override int Compare(IBoundable<Envelope, TItem> o1, IBoundable<Envelope, TItem> o2)
			{
				return AbstractSTRtree<Envelope, TItem>.CompareDoubles(STRtree<TItem>.CentreY(o1.Bounds), STRtree<TItem>.CentreY(o2.Bounds));
			}
		}

		[Serializable]
		private class AnonymousAbstractNodeImpl : AbstractNode<Envelope, TItem>
		{
			public AnonymousAbstractNodeImpl(int nodeCapacity) : base(nodeCapacity)
			{
			}

			protected override Envelope ComputeBounds()
			{
				Envelope envelope = new Envelope();
				foreach (IBoundable<Envelope, TItem> boundable in base.ChildBoundables)
				{
					envelope.ExpandToInclude(boundable.Bounds);
				}
				if (!envelope.IsNull)
				{
					return envelope;
				}
				return null;
			}
		}

		private class AnonymousIntersectsOpImpl : AbstractSTRtree<Envelope, TItem>.IIntersectsOp
		{
			public bool Intersects(Envelope aBounds, Envelope bBounds)
			{
				return aBounds.Intersects(bBounds);
			}
		}
	}
}
